#include<iostream>
#include<string>
#include<unistd.h>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<sys/wait.h>
#include<sys/types.h>
#include<ctype.h>


using namespace std;

const int basesize = 1024;
const int argvnum = 64;
const int envnum = 64;

//全局的命令行参数表
char* gargv[argvnum];
int gargc = 0;

//全局的变量
int lastcode = 0;

//我系统的环境变量
char* genv[envnum];

//全局当前shell工作路径
char pwd[basesize];
char pwdenv[basesize];

string GetUserName()
{
    string name = getenv("USER");//利用库函数获取用户名
    return name.empty() ? "None" : name;
}

string GetHostName()
{
    string HostName = getenv("HOSTNAME");
    return HostName.empty() ? "None" : HostName;
}

string GetPWD()
{
    if(nullptr == getcwd(pwd, basesize)) return "Node";//getcwd用于获取当前目录的绝对路径 参数：缓冲区+大小
    snprintf(pwdenv, sizeof(pwdenv), "PWD = %s", pwd);//pwdenv来作为缓冲区存储pwd
    putenv(pwdenv);
    return pwd;
}

string LastDir()
{
    string cur = GetPWD();//获取当前路径
    if(cur == "/" || cur == "None") return cur;
    //不是根目录 ~/kk/test/XXX
    size_t pos = cur.rfind("/");
    if(pos == std::string::npos) return cur;
    return cur.substr(pos + 1);//pos+1到最后即lastdir
}

string MakeCommandLine()
{
    //ckl@hcss-ecs-42fa:~/Linuxreview/testcode$
    char commandline[basesize];
    snprintf(commandline, basesize, "%s@%s %s", GetUserName().c_str(), GetHostName().c_str(), LastDir().c_str());
    return commandline;
}

void PrintCommandLine()//打印命令提示行
{
    printf("%s", MakeCommandLine().c_str());
    fflush(stdout);
}

bool GetCommandLine(char commandline_buf[], int size)//获取用户的命令、
{
//我们要将⽤⼾输⼊的命令⾏，当做⼀个完整的字符串
// "ls -a -l -n"
    char *result = fgets(commandline_buf, size, stdin);
    if(!result)
    {
        return false;
    }
    commandline_buf[strlen(commandline_buf)-1] = 0;
    if(strlen(commandline_buf) == 0) return false;
    return true;
}

void ParseCommandLine(char command_buf[], int len)
{
    (void) len;
    memset(gargv, 0, sizeof(gargv));
    gargc = 0;

    const char* stp = " ";
    gargv[gargc++] = strtok(command_buf, stp);//字符串分割函数，用于按照指定的分隔符将一个字符串拆分成多个子串
    while((bool)(gargv[gargc++] = strtok(nullptr, stp)));//后续调用传入 nullptr 表示继续分割上一次剩余的部分
    gargc--;
}

void debug()
{
    printf("argc: %d\n", gargc);

    for(int i = 0; gargv[i]; i++)
    {
        printf("argv[%d]: %s\n", i, gargv[i]);
    }
}

// 在shell中
// 有些命令，必须由⼦进程来执⾏
// 有些命令，不能由⼦进程执⾏，要由shell⾃⼰执⾏ --- 内建命令 built command

bool ExecuteCommand()
{
    //执行命令
    pid_t id = fork();
    if(id < 0) return false;
    if(id == 0)
    {
        //child
        execvpe(gargv[0], gargv, genv);
        //exit
        exit(1);
    }

    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if(rid > 0)
    {
        if(WIFEXITED(status))
        {
            lastcode = WEXITSTATUS(status);
        }
        else
        {
            lastcode = 100;
        }
        return true;
    }
    return false;
}

void AddEnv(const char* item)
{
    int index = 0;
    while(genv[index])
    {
        index++;
    }
    //到达最末端
    genv[index] = (char*)malloc(strlen(item) + 1);
    strncpy(genv[index], item, strlen((item) + 1));
    genv[++index] = nullptr;
}

// shell⾃⼰执⾏命令，本质是shell调⽤⾃⼰的函数
bool CheckAndExecBuiltCommand() {
  if (strcmp(gargv[0], "cd") == 0) {
    // 内建命令
    if (gargc == 2) {
      chdir(gargv[1]);
      lastcode = 0;
    } else {
      lastcode = 1;
    }
    return true;
  } else if (strcmp(gargv[0], "export") == 0) {
    // export也是内建命令
    if (gargc == 2) {
      AddEnv(gargv[1]);
      lastcode = 0;
    } else {
      lastcode = 2;
    }
    return true;
  } else if (strcmp(gargv[0], "env") == 0) {
    for (int i = 0; genv[i]; i++) {
      printf("%s\n", genv[i]);
    }
    lastcode = 0;
    return true;
  } else if (strcmp(gargv[0], "echo") == 0) {
    if (gargc == 2) {
      // echo $?
      // echo $PATH
      // echo hello
      if (gargv[1][0] == '$') {
        if (gargv[1][1] == '?') {
          printf("%d\n", lastcode);
          lastcode = 0;
        }
      } else {
        printf("%s\n", gargv[1]);
        lastcode = 0;
      }
    } else {
      lastcode = 3;
    }
    return true;
  }
  return false;
}

// 作为⼀个shell，获取环境变量应该从系统的配置来
// 我们今天就直接从⽗shell中获取环境变量
void InitEnv() {
  extern char **environ;
  int index = 0;
  while (environ[index]) {
    genv[index] = (char *)malloc(strlen(environ[index]) + 1);
    strncpy(genv[index], environ[index], strlen(environ[index]) + 1);
    index++;
  }
  genv[index] = nullptr;
}
int main() {
  InitEnv();
  char command_buffer[basesize];
  while (true) {
    PrintCommandLine(); // 1. 命令⾏提⽰符
    // command_buffer -> output
    if (!GetCommandLine(command_buffer, basesize)) // 2. 获取⽤⼾命令
    {
      continue;
    }
    // printf("%s\n", command_buffer);
    ParseCommandLine(command_buffer, strlen(command_buffer)); // 3. 分析命令
    if (CheckAndExecBuiltCommand()) {
      continue;
    }
    ExecuteCommand(); // 4. 执⾏命令
  }
  return 0;
}
